/*
XPTracker - Alternative Agile Tracking Tool
Copyright (C) 2006-2009  Stephen Starkey

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/
package org.xptracker.domain;

import com.Ostermiller.util.MD5;
import flexjson.JSON;
import org.apache.commons.lang.RandomStringUtils;
import org.hibernate.annotations.*;
import org.xptracker.persistence.Identified;

import javax.persistence.*;
import javax.persistence.Entity;
import javax.persistence.MapKey;
import javax.persistence.Table;
import java.util.*;

@Entity
@Table(name="users")
@Cache(usage= CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Proxy(lazy=false)
public class User extends Identified implements UserAccessor, Comparable<User> {
  private String username;
  private String hashedPassword;
  private Set<String> roles = new HashSet<String>();
  private String fullName;
  private String email;
  private String phoneNumber;
  private String title;
  private List<Task> tasks = new LinkedList<Task>();
  private List<Queue> queues = new LinkedList<Queue>();
  private List<Timeline> timelines = new LinkedList<Timeline>();
  private List<Story> bookmarkedStories = new LinkedList<Story>();
  private Set<SavedQuery> queries;
  private Set<String> homepageQueries;
  private int blogEntryLimit = 6;
  private boolean enabled = true;
  private Boolean emailMeMyChanges = true;

  public User() {
    this(RandomStringUtils.randomAlphabetic(8));
  }

  public User(String username) {
    setUsername(username);
  }

  @Column
  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    if (username == null) {
      throw new IllegalArgumentException("Username may not be null.");
    }
    this.username = username;
  }

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }

  @Column(name="full_name")
  public String getFullName() {
    return fullName;
  }

  public void setFullName(String fullName) {
    this.fullName = fullName;
  }

  @Column(name="phone_number")
  public String getPhoneNumber() {
    return phoneNumber;
  }

  public void setPhoneNumber(String phoneNumber) {
    this.phoneNumber = phoneNumber;
  }

  /**
   * This is now actually the number of stories listed on dashboard
   * @return number of stories listed on dash
   * todo rename this to be more appropriate
   */
  @JSON(include=false)
  @Column(name="dashboard_story_limit")
  public int getBlogEntryLimit() {
    return blogEntryLimit;
  }

  public void setBlogEntryLimit(int blogEntryLimit) {
    this.blogEntryLimit = blogEntryLimit;
  }

  @JSON(include=false)
  @Column(name="hashed_pwd")
  public String getHashedPassword() {
    return hashedPassword;
  }

  private void setHashedPassword(String hashedPassword) {
    this.hashedPassword = hashedPassword;
  }

  public void setPassword(String password) {
    setHashedPassword(MD5.getHashString(password));
  }

  public boolean passwordMatches(String checkPassword) {
    return MD5.getHashString(checkPassword).equals(hashedPassword);
  }

  @CollectionOfElements(fetch=FetchType.EAGER)
  @JoinTable(name="user_roles", joinColumns = @JoinColumn(name="user_id"))
  @MapKey(name="role")
  @Fetch(FetchMode.JOIN)
  protected Set<String> getRoles() {
    return roles;
  }

  protected void setRoles(Set<String> roles) {
    this.roles = roles;
  }                      

  public void addRole(Role role) {
    roles.add(role.toString());
  }

  public void removeRole(Role role) {
    roles.remove(role.toString());
  }

  public boolean hasRole(Role role) {
    return roles.contains(role.toString());
  }

  public String toString() {
    return "User " + getId() + ": '" + username + "'";
  }

  @OneToMany()
  @JoinTable(name="user_tasks", joinColumns = @JoinColumn(name="user_id"),
        inverseJoinColumns = @JoinColumn(name="task_id"))
  public List<Task> getTasks() {
    //todo Remove tasks from users forever
    if (tasks == null) {
      tasks = new LinkedList<Task>();
    }
    return tasks;
  }

  public void setTasks(List<Task> tasks) {
    this.tasks = tasks;
  }

  @ManyToMany()
  @IndexColumn(name="user_queue_idx")
  @JoinTable(name="user_queues_mapping", joinColumns = @JoinColumn(name="user_id"),
        inverseJoinColumns = @JoinColumn(name="queue_id"))
  public List<Queue> getQueues() {
    if (queues == null) {
      queues = new LinkedList<Queue>();
    }
    return queues;
  }

  public void setQueues(List<Queue> queues) {
    this.queues = queues;
  }

  @ManyToMany
  @IndexColumn(name="bmk_story_idx")
  @JoinTable(name="user_bookmarked_stories_mapping", joinColumns=@JoinColumn(name="user_id"),
      inverseJoinColumns=@JoinColumn(name="story_id"))
  public List<Story> getBookmarkedStories() {
    if (bookmarkedStories == null) {
      bookmarkedStories = new LinkedList<Story>();
    }
    return bookmarkedStories;
  }

  public void setBookmarkedStories(List<Story> bookmarkedStories) {
    this.bookmarkedStories = new LinkedList<Story>(bookmarkedStories);
  }

  public void addBookmark(Story story) {
    if (bookmarkedStories.contains(story)) {
      removeBookmark(story);
    }
    bookmarkedStories.add(story);
    setBookmarkedStories(bookmarkedStories);
  }

  public void removeBookmark(Story story) {
    bookmarkedStories.remove(story);
    setBookmarkedStories(bookmarkedStories);
  }

  @OneToMany()
  @JoinTable(name="user_saved_queries_mapping", joinColumns = @JoinColumn(name="user_id"),
        inverseJoinColumns = @JoinColumn(name="query_id"))
  public Set<SavedQuery> getQueries() {
    if (queries == null) {
      queries = new LinkedHashSet<SavedQuery>();
    }
    return queries;
  }

  protected void setQueries(Set<SavedQuery> queries) {
    this.queries = queries;
  }

  public boolean hasQuery(String name) {
    for (SavedQuery q : getQueries()) {
      if (q.getName().equals(name)) {
        return true;
      }
    }
    return false;
  }

  public SavedQuery addQuery(String name, String query) {
    SavedQuery queryObj = new SavedQuery(name, query, this);
    getQueries().add(queryObj);
    return queryObj;
  }

  public SavedQuery removeQuery(String name) {
    Iterator<SavedQuery> queriesI = getQueries().iterator();
    while (queriesI.hasNext()) {
      SavedQuery q = queriesI.next();
      if (q.getName().equals(name)) {
        queriesI.remove();
        return q;
      }
    }
    return null;
  }

  @CollectionOfElements()
  @JoinTable(name="user_homepage_queries", joinColumns = @JoinColumn(name="user_id"))
  @MapKey(name="query")
  public Set<String> getHomepageQueries() {
    if (homepageQueries == null) {
      homepageQueries = new HashSet<String>();
    }
    return homepageQueries;
  }

  public void setHomepageQueries(Set<String> homepageQueries) {
    this.homepageQueries = homepageQueries;
  }

  @ManyToMany()
  @JoinTable(name="user_timelines_mapping", joinColumns = @JoinColumn(name="user_id"),
        inverseJoinColumns = @JoinColumn(name="category_id"))
  @IndexColumn(name="user_timeline_idx")
  public List<Timeline> getTimelines() {
    return timelines;
  }                               

  public void setTimelines(List<Timeline> timelines) {
    this.timelines = timelines;
  }

  @JSON(include=false)
  @Column
  public boolean isEnabled() {
    return enabled;
  }

  public void setEnabled(boolean enabled) {
    this.enabled = enabled;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  @Column(name="email_me_my_changes")
  public Boolean getEmailMeMyChanges() {
    return emailMeMyChanges != null && emailMeMyChanges;
  }

  public void setEmailMeMyChanges(Boolean emailMeMyChanges) {
    this.emailMeMyChanges = emailMeMyChanges != null && emailMeMyChanges;
  }

  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    if (!super.equals(o)) return false;

    User user = (User) o;

    return username.equals(user.username);
  }

  public int hashCode() {
    int result = super.hashCode();
    result = 31 * result + username.hashCode();
    return result;
  }

  public static boolean hasRole(User user, Role role) {
    return user.hasRole(role);
  }

  public int compareTo(User user) {
    return username.compareTo(user.getUsername());
  }
}
